TimerLibAnchor = {};

local GetTime = GetTime;
local huge = math.huge;
local lower = string.lower;

local groupModule = TimerLibGroup;

local function updateTimersFunc(self, elapsed)
    local groups = self.anchor.groups;
    local time = GetTime();
    local needsResize = false;
    local allGhost = true;
    for i, group in ipairs(groups) do
        for timer in group:GetTimers() do
            if (not timer.isGhost) and (timer.duration) and (time >= timer.time + timer.duration) then
                group:RemoveTimer(timer);
            end
            local timerNeedsResize = (not timer.hidden and timer:Update(time) or false);
            needsResize = needsResize or timerNeedsResize;
        end
        
        if #group.timers > 0 then
            if group.timerSortMethod == "Percent Remaining" then
                local w, h = group:SortTimers();
                if needsResize then group:Resize(w, h); end
            elseif needsResize then
                group:Resize();
            end
            allGhost = allGhost and group.allGhost;
        end
    end
    if self.anchor:Get("anchorsToCursor") and not allGhost and self.anchor.canUpdatePosition then
        self.anchor:UpdateAnchorToCursor();
        self.anchor.canUpdatePosition = false;
    end
end

local groupSort = function(a, b)
    if a.priority ~= b.priority then
        return (a.priority or huge) < (b.priority or huge);
    end
    local method, direction = a.groupSortMethod, a.groupSortDirection;
    local aComp, bComp;
    if method == "Time Added" then
        aComp, bComp = a.time, b.time;
    elseif method == "Alphabetical" then
        aComp, bComp = a.displayText, b.displayText;
        if aComp == bComp then
            aComp, bComp = a.time, b.time;
        end
    end
    
    if direction == "D" then
        return (bComp < aComp);
    else
        return (aComp < bComp);
    end
end

local onUpdateFunc = function(self)
    if not MouseIsOver(self) then
        self:GetParent().mouseIsOver = false;
        self:EnableMouse(true);
        self.group.anchor:UpdateAlpha();
        self:SetScript("OnUpdate", nil);
    end
end

local onEnterFunc = function(self)
    self:GetParent().mouseIsOver = true;
    self:EnableMouse(false);
    self.group.anchor:UpdateAlpha();
    self:SetScript("OnUpdate", onUpdateFunc);
end

local anchorFuncs = {
    AddTimer = function(self, timer, groupdata, priority, hidden)
        local group = self:FindGroup(groupdata);
        if group then
            group.data = groupdata;
            group:UpdateData();
            group.priority = priority;
            group.hidden = hidden;
            group:AddTimer(timer);
            return group;
        end
        
        group = groupModule:NewGroup(groupdata);
        group.frame:SetParent(self.frame);
        group.priority = priority;
        group.hidden = hidden;
        group.frame:EnableMouse(true);
        group.frame:SetScript("OnEnter", onEnterFunc);
        self:AddGroup(group);
        group:AddTimer(timer);
        return group;
    end,
    
    FindGroup = function(self, groupdata)
        local groups = self.groups;
        for _, group in ipairs(groups) do
            if (group.data == groupdata) or (type(groupdata) == "table" and group.uniqueID and group.uniqueID == groupdata.uniqueID) then
                return group;
            end
        end
    end,
    
    AddGroup = function(self, group)
        group.anchor = self;
        group.index = nil;
        group:UpdateData();
        table.insert(self.groups, group);
        self.frame:SetScript("OnUpdate", updateTimersFunc);
        self:SortGroups();
        if self:Get("centering") ~= "none" then
            self:Center();
        end
    end,

    SortGroups = function(self)
        local groups = self.groups;
        table.sort(groups, groupSort);
        local alreadySorted = true;
        for i, group in ipairs(groups) do
            if group.index ~= i then
                alreadySorted = false;
                group.index = i;
            end
        end
        -- now that they're sorted, reanchor them!
        if not alreadySorted then self:ReanchorGroups(); end
    end,

    ReanchorGroups = function(self)
        local groups = self.groups;
        if #groups > 0 then
            local ref1, ref2;
            local dir = self:Get("groupDirection");
            local timerDir = self:Get("timerDirection");
            local overflowDir = self:Get("overflowDirection");
            local justify = self:Get("timerJustification");
            local spacing = self:Get("groupSpacing");
            local x, y;
            if dir == "down" then
                ref1, ref2 = "TOPLEFT", "BOTTOMLEFT";
                if timerDir == "left" or overflowDir == "left" or justify == "right" then
                    ref1, ref2 = "TOPRIGHT", "BOTTOMRIGHT";
                end
                x, y = 0, -spacing;
            elseif dir == "up" then
                ref1, ref2 = "BOTTOMLEFT", "TOPLEFT";
                if timerDir == "left" or overflowDir == "left" or justify == "right" then
                    ref1, ref2 = "BOTTOMRIGHT", "TOPRIGHT";
                end
                x, y = 0, spacing;
            elseif dir == "left" then
                ref1, ref2 = "TOPRIGHT", "TOPLEFT";
                if timerDir == "up" or overflowDir == "up" or justify == "buttom" then
                    ref1, ref2 = "BOTTOMRIGHT", "BOTTOMLEFT";
                end
                x, y = -spacing, 0;
            elseif dir == "right" then
                ref1, ref2 = "TOPLEFT", "TOPRIGHT";
                if timerDir == "up" or overflowDir == "up" or justify == "bottom" then
                    ref1, ref2 = "BOTTOMLEFT", "BOTTOMRIGHT";
                end
                x, y = spacing, 0;
            end
            local first = 1;
            while (first <= #groups and (groups[first].hidden or groups[first].allHidden)) do
                groups[first].frame:Hide();
                first = first + 1;
            end
            if first <= #groups then
                groups[first].frame:Show();
                groups[first].frame:ClearAllPoints();
                groups[first].frame:SetPoint(ref1, self.frame, ref1);
                local maxNum = self:Get("maxNumGroups");
                local lastGroup = groups[first];
                for i = first + 1, #groups do
                    if (i <= maxNum or maxNum == 0) and not (groups[i].hidden or groups[i].allHidden) then
                        groups[i].frame:Show();
                        groups[i].frame:ClearAllPoints();
                        groups[i].frame:SetPoint(ref1, lastGroup.frame, ref2, x, y);
                        lastGroup = groups[i];
                    else
                        groups[i].frame:Hide();
                    end
                end
            end
        end
    end,

    RemoveGroup = function(self, removed)
        local groups = self.groups;
        for i, group in ipairs(groups) do
            if (group == removed) then
                table.remove(groups, i);
                self:SortGroups();
                break;
            end
        end
    end,
    
    RemoveAllGroups = function(self)
        local groups = self.groups;
        for i = #groups, 1, -1 do
            local group = groups[i];
            for timer in group:GetTimers() do group:DestroyTimer(timer) end
        end
    end,
    
    Get = function(self, ...)
        local val = TimerLib:Get("anchorSettings", self.settings, ...);
        if val == nil then
            val = TimerLib:Get("anchorSettings", "Default Anchor", ...);
        end
        return val;
    end,
    
    Set = function(self, ...)
        TimerLib:Set("anchorSettings", self.settings, ...);
    end,
    
    SetupFrame = function(self)
        local f = self.frame;
        local a = f.anchorFrame;
        a:SetMovable(not self:Get("locked"));
        a:ClearAllPoints();
        f:SetScale(self:Get("scale"));
        if self:Get("hideAll") then
            f:Hide();
        else
            f:Show();
        end
        if self:Get("anchorsToCursor") then
            self:UpdateAnchorToCursor();
        else
            local x, y = self:Get("positionX"), self:Get("positionY");
            if x or y then
                x, y = (x or 0), (y or 0);
                a:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", x, y);
            else
                x, y = math.random(-5, 5), math.random(-5, 5);
                a:SetPoint("CENTER", UIParent, "CENTER", x, y);
            end
        end
        
        if self:Get("centering") ~= "none" then
            self:Center();
        end
    end,
    
    UpdateAnchorToCursor = function(self)
        local x, y = GetCursorPosition();
        local scale = UIParent:GetScale();
        x, y = (x + 20) / scale, (y - 10) / scale;
        self.frame.anchorFrame:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", x, y);
    end,
    
    Center = function(self)
        -- first, calculate the total width / height of the anchor
        local width, height = 0, 0;
        local dir = self:Get("groupDirection");
        local spacing = self:Get("groupSpacing");
        for i, group in ipairs(self.groups) do
            if (dir == "up" or dir == "down") then
                width = math.max(width, group.frame:GetWidth());
                height = height + group.frame:GetHeight() + (i == 1 and 0 or spacing);
            else
                width = width + group.frame:GetWidth() + (i == 1 and 0 or spacing);
                height = math.max(height, group.frame:GetHeight());
            end
        end
        
        width = width * self.frame:GetScale();
        height = height * self.frame:GetScale();
        local centering = self:Get("centering");
        local f = self.frame.anchorFrame;
        if self.groups[1] then
            local point, relPoint = self.groups[1].frame:GetPoint();
            local vert = string.match(point, "(.*)LEFT") or string.match(point, "(.*)RIGHT");
            local horz = string.match(point, "BOTTOM(.*)") or string.match(point, "TOP(.*)");
            local hMod, vMod = 1, 1;
            if (vert == "BOTTOM") then vMod = -1; end
            if (horz == "LEFT") then hMod = -1; end
            
            if centering == "vertical" then
                local x = f:GetLeft();
                local y = (UIParent:GetHeight() + (vMod * height)) / 2;
                f:ClearAllPoints();
                f:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", x, y);
            elseif centering == "horizontal" then
                local x = (UIParent:GetWidth() + (hMod * width)) / 2;
                local y = f:GetTop();
                f:ClearAllPoints();
                f:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", x, y);
            end
        end
    end,
    
    UpdateAlpha = function(self)
        local alpha;
        if self.frame.mouseIsOver then
            alpha = self:Get("mouseoverAlpha");
        else
            alpha = UnitAffectingCombat("player") and self:Get("combatAlpha") or self:Get("standardAlpha");
        end
        self.frame:SetAlpha(alpha);
    end,
    
    UpdateSettings = function(self)
        self:UpdateAlpha();
        for _, group in ipairs(self.groups) do
            group:UpdateSettings();
        end
        if self.groups[1] then
            self.groups[1].index = nil;
        end
        self:SortGroups();
        self:SetupFrame();
    end,
};

function TimerLibAnchor:NewAnchor(settings)
    local anchor = TimerLib:AcquireTable();
    anchor.groups = TimerLib:AcquireTable();
    local a = CreateFrame("Frame", "omg!" .. GetTime(), UIParent, "TimerLibAnchorFrameAnchorTemplate");
    local f = CreateFrame("Frame", nil, a, "TimerLibAnchorFrameTemplate");
    f:SetPoint("TOPLEFT", a, "TOPLEFT");
    anchor.frame = f;
    f.anchor = anchor;
    f.anchorFrame = a;
    f:RegisterEvent("PLAYER_REGEN_ENABLED");
    f:RegisterEvent("PLAYER_REGEN_DISABLED");
    f:SetScript("OnEvent", function(self) anchor:UpdateAlpha() end);
    --[[
    local t=f:CreateTexture();
    t:SetAllPoints(true);
    t:SetTexture(1, 0, 0, .5);--]]
    anchor.settings = settings or "Default Anchor";
    for i, v in pairs(anchorFuncs) do
        anchor[i] = v;
    end
    
    anchor:SetupFrame();
    anchor:UpdateAlpha();
    
    if not TimerLib:Get("anchorSettings", settings) then
        TimerLib:Set("anchorSettings", settings, {});
    end
    return anchor;
end
